#pragma once

#include <string>
#include <mutex>
#include <unordered_set>
#include <memory>
#include <vector>


namespace evm {

using namespace std;

/* An interprocess locking implementation.
 *
 * Since the lock is always held on a file descriptor rather than outside
 * of the process, the lock gets dropped automatically if the process crashes.
 *
 * There are no guarantees regarding usage by multiple threads in a
 * single process here. This lock works only between processes.
 *
 * Note these locks are released when the descriptor is closed, so it's not
 * safe to close the file descriptor while another thread holds the lock. */
struct InterProcessLock {
    InterProcessLock(const string& path)
        : path_(path) {}

    ~InterProcessLock();

    /* Attempt to acquire the given lock. */
    bool acquire(bool block = true, long timeout = -1,
                 double incr_delay = 0.01, double max_delay = 0.1);

    /* Release the previously acquired lock. */
    void release();

private:
    void open();

    void close();

    bool lock();

    void unlock();

    string path_;
    int fd_ = -1;
    bool acquired_ = false;
};

/* Lock for a specified name, which supplies inter-thread locking and inter-process locking.
 * `name` will be guarded by lock across threads and processes.
 * `lock_path` is the directory where the lock file will be placed.
 * Inter-process locking is optional. If it is disabled, only the inter-thread locking takes effect. */
struct NameLock {
    /* Inter-thread name lock. */
    NameLock(const string& name)
        : NameLock(name, shared_ptr<InterProcessLock>()) {}

    /* Inter-thread and inter-process name lock, with lock file directory. */
    NameLock(const string& name, const string& lock_path);

    NameLock(const string& name, shared_ptr<InterProcessLock> process_lock);

    ~NameLock();

    static void remove_lock_file(const string& name, const string& lock_path);

    /* Returns all the global name locks, for debug purpose. */
    static vector<string> names();

private:
    void lock();
    void unlock();

    struct _NameLock {
        _NameLock(const string& name) : name_(name) {}

        string name_;
        mutex mutex_;
    };

    using _NameLockPtr = shared_ptr<_NameLock>;

    struct hash {
        size_t operator()(const _NameLockPtr& item) const noexcept {
            static std::hash<string> h;
            return h(item->name_);
        }
    };

    struct equal {
        bool operator()(const _NameLockPtr& lhs, const _NameLockPtr& rhs) const noexcept {
            return lhs->name_ == rhs->name_;
        }
    };

    static unordered_set<_NameLockPtr, hash, equal> namelocks_;
    static mutex namelocks_mutex_;

    _NameLockPtr namelock_;  // inter-thread lock for name
    shared_ptr<InterProcessLock> process_lock_;  // inter-process lock for name
};

}
